#include <os/exec.h>
#include <os/elf32.h>
#include <os/debug.h>
#include <os/task.h>
#include <os/schedule.h>
#include <os/process.h>
#include <os/kernelif.h>
#include <os/fs.h>
#include <os/path.h>
#include <os/vmm.h>
#include <arch/task.h>
#include <lib/unistd.h>
#include <lib/type.h>
#include <sys/fcntl.h>

void DumpHeader(elf32_header_t *header)
{
    int i;

    KPrint("ElfHeader: ident:\n");
    KPrint("magic:");
    for (int i = 0; i < 4; i++)
    {
        KPrint("%x ", (uint32_t)header->e_ident.MAGIC[i]);
    }
    KPrint("\n");
    switch (header->e_ident.class)
    {
    case 0:
        KPrint("class: unknow\n");
        break;
    case 1:
        KPrint("class: bits32\n");
        break;
    case 2:
        KPrint("class: bits64\n");
    default:
        break;
    }
    switch (header->e_ident.endian)
    {
    case 0:
        KPrint("edian: unknow\n");
        break;
    case 1:
        KPrint("edian: little edian\n");
        break;
    case 2:
        KPrint("edian: big edian\n");
        break;
    default:
        break;
    }
    switch (header->e_ident.OSABI)
    {
    case ELF_OSABI_SOLARIS:
        KPrint(PRINT_DEBUG "OSABI: solaris\n");
        break;
    case ELF_OSABI_STANDALONE:
        KPrint(PRINT_DEBUG "OSABI: standalone\n");
        break;
    case ELF_OSABI_ARM:
        KPrint(PRINT_DEBUG "OSABI: arm\n");
        break;
    case ELF_OSABI_FREEBSD:
        KPrint(PRINT_DEBUG "OSABI: freebsd\n");
        break;
    case ELF_OSABI_IRIX:
        KPrint(PRINT_DEBUG "OSABI: irix");
        break;
    case ELF_OSABI_LINUX:
        KPrint(PRINT_DEBUG "OSABI: llnux\n");
        break;
    case ELF_OSABI_OPENBSD:
        KPrint(PRINT_DEBUG "OSABI: openbsd\n");
        break;
    default:
        KPrint("OSABI: unknow\n");
        break;
    }
    switch (header->e_type)
    {
    case ELFTYPE_EXEC:
        KPrint("type: execuable\n");
        break;
    case ELFTYPE_CORE:
        KPrint("type: core\n");
        break;
    case ELFTYPE_DYNC:
        KPrint("type: dync\n");
        break;
    case ELFTYPE_REL:
        KPrint("type: rel\n");
        break;
    default:
        break;
    }

    switch (header->e_machie)
    {
    case ELF_X86:
        KPrint("machine: x86\n");
        break;
    case ELF_X86_64:
        KPrint("machine: x86_64\n");
        break;
    case ELF_ARCHUNKNOW:
        KPrint("machine: unknown architure\n");
        break;
    default:
        KPrint("machine: no support architure type! only support intel CPU\n");
        break;
    }
    KPrint("phnum: %d\n", header->e_phnum);
    KPrint("size: %d\n", header->e_phensize);
    KPrint("version: %x\n", header->e_verison);
}

void DumpArgs(char **argv, char **envp)
{
    int i;
    i = 0;
    while (argv[i])
    {
        KPrint("arg %s\n", argv[i]);
        i++;
    }
    i = 0;
    while (envp[i])
    {
        KPrint("env %s\n", envp[i]);
        i++;
    }
}

static int DoExec(const char *path, const char *name, const char *argv[], const char *envp[])
{
    task_t *cur = cur_task;
    status_t status;
    int fd;
    char **new_envp;
    char **new_argv;
    char *tmp_arg;
    uint64_t arg_bottom;
    trap_frame_t *frame;

    ProcessCloseOtherThread(cur);
    // open file

    fd = KFileOpen(path, O_RDONLY);
    if (fd < 0)
    {
        KPrint(PRINT_ERR "[exec]: %s open failed!\n", path);
        goto free_task_arg;
    }
    if (KFileStatus(path, &status))
    {
        KPrint(PRINT_ERR "[exec]: %s file status failed!\n", path);
        goto free_file_fd;
    }
    if (!status.st_size)
    {
        KPrint(PRINT_ERR "[exec]: %s file size is zero!\n", path);
        goto free_file_fd;
    }
// parse elf32 header
#if __ARCH == X86
    elf32_header_t elf_header;
    // read elf header
    memset(&elf_header, 0, sizeof(elf_header));
    KFileLSeek(fd, 0, SEEK_SET);
    if (KFileRead(fd, &elf_header, sizeof(elf32_header_t)) < 0)
    {
        KPrint(PRINT_ERR "[exec]: read elf header failed!\n");
        goto free_file_fd;
    }

    // DumpHeader(&elf_header);
    if (memcmp(elf_header.e_ident.MAGIC + 1, "ELF", 3) ||
        elf_header.e_type != ELFTYPE_EXEC ||
        elf_header.e_machie != ELF_X86 ||
        elf_header.e_verison != ELF_VERSION_CURRENT ||
        elf_header.e_phnum > ELF_PHMAX ||
        elf_header.e_phensize != sizeof(elf32_segment_t))
    {
        KPrint(PRINT_ERR "[exec] parse elf header error!\n");
        goto free_file_fd;
    }
    KPrint("[elf] header parse ok!\n");
#endif
    // alloc tmp arg
    tmp_arg = KMemAlloc(PAGE_SIZE);
    if (!tmp_arg)
    {
        KPrint(PRINT_ERR "%s: task %s malloc for tmp arg failed!\n", __func__, name);
        goto free_file_fd;
    }
    // according if is had argbuff build
    if (cur->vmm->argbuff)
    {
        ProcessBuildArg((uint32_t)PTYPE(tmp_arg + PAGE_SIZE), &arg_bottom, (const char **)cur->vmm->envp, (const char ***)&new_envp);
        ProcessBuildArg(arg_bottom, NULL, (const char **)cur->vmm->argu, (const char ***)&new_argv);
    }
    else
    {
        ProcessBuildArg((uint32_t)PTYPE(tmp_arg + PAGE_SIZE), &arg_bottom, (const char **)envp, (const char ***)&new_envp);
        ProcessBuildArg(arg_bottom, NULL, (const char **)argv, (const char ***)&new_argv);
    }
    // unmap only map area
    VmmUnMapOnlyMapSpace(cur->vmm);
    VmmReleaseSpace(cur->vmm);

    DumpArgs(new_argv, new_envp);

    // load image
    if (ProcessLoadImage(cur->vmm, &elf_header, fd) < 0)
    {
        KPrint("%s: load image failed!\n", __func__);
        goto free_tmp_arg;
    }

    // init frame
    frame = (trap_frame_t *)TASK_GET_TRAP_FRAME(cur);
    ProcessTrapFrameInit(cur);

    if (ProcessFrameInit(cur, frame, new_argv, new_envp) < 0)
        goto free_tmp_arg;

    if (tmp_arg)
        KMemFree(tmp_arg);
    KFileClose(fd);
    // process exec init
    ProcessExecInit(cur);
    UserSetEntryPoint(frame, elf_header.e_entry);

    // set name
    memset(cur->name, 0, TASK_NAME_LEN);
    strcpy(cur->name, name);

    KPrint("[process] start exec %s entry %p\n", cur->name, frame->eip);

    KernelSwitchToUser(frame);
    return 0;
free_loaded_image:
    SysExit(-1);
free_tmp_arg:
    if (tmp_arg)
    {
        KMemFree(tmp_arg);
    }
free_file_fd:
    KFileClose(fd);
free_task_arg:
    VmmDeBuildArgBuff(cur->vmm);
    return -1;
}

int SysExec(const char *path, const char *argv[], const char *envp[])
{
    char *p = (char *)path;
    char *q = NULL;
    char **env;
    char *name;
    char newpath[MAX_PATH_LEN+1];
    char finalpath[MAX_PATH_LEN+1];

    if (!path)
        return -1;

    memset(newpath, 0, MAX_PATH_LEN);

    if (*p == '/') // abspath
    {
        WashPath(p, newpath);
        if (!KFileAccess(newpath, F_OK))
        {
            name = (char *)strrchr(newpath, '/');
            if (name)
            {
                name++;
            }
            else
            {
                name = newpath;
            }

            if (DoExec(newpath, name, argv, envp) < 0)
            {
                KPrint(PRINT_ERR "%s: path %s not executable!\n", __func__, newpath);
                return -1;
            }
        }
        else
        {
            KPrint("%s: path %s no acess\n", __func__, path);
            return -1;
        }
    }
    else
    {
        // rebuild to current director
        if ((*p == '.' && *(p + 1) == '/') || (*p == '.' && *(p + 1) == '.' && *(p + 2) == '/'))
        {
            BuildPath(p, newpath);
            if (!KFileAccess(newpath, F_OK))
            {
                name = (char *)strrchr(newpath, '/');
                if (name)
                    name++;
                else
                    name = newpath;

                if (DoExec(newpath, name, argv, envp))
                {
                    KPrint("%s: path %s not exec!\n", __func__, newpath);
                    return -1;
                }
            }
            else
            {
                KPrint("%s: path %s no acess\n", __func__, path);
                return -1;
            }
        }
        else
        {
            // search envp
            if (envp)
            {
                env = (char **)envp;
                while (*env)
                {
                    q = *env; // get evnp
                    strcpy(newpath, q);
                    if (newpath[strlen(newpath) - 1] != '/')
                    {
                        strcat(newpath, "/");
                    }
                    strcat(newpath, p); // make path in evnp

                    // parse path
                    strchr(newpath, (char)'/');
                    WashPath(newpath, finalpath);
                    if (!KFileAccess(finalpath, F_OK))
                    {
                        name = (char *)strrchr(finalpath, '/');
                        if (name)
                            name++;
                        else
                            name = finalpath;

                        if (DoExec(finalpath, name, argv, envp) < 0)
                        {
                            KPrint(PRINT_ERR "%s: path %s not exec!\n", __func__, path);
                        }
                    }
                    // next envp
                    env++;
                    memset(newpath, 0, MAX_PATH_LEN);
                }
            }
            else
            {
                // direct exec
                if (DoExec(path, path, argv, envp))
                {
                    KPrint(PRINT_ERR "%s: path %s not executale!\n", __func__, path);
                    return -1;
                }
            }
        }
    }
}
